package com.baishancloud.log.analyzer.model

import scala.collection.mutable.ListBuffer

/**
 * Type represents media types according to [[https://tools.ietf.org/html/rfc7231#section-3.1.1.1 RFC7231]]
 */
case class MediaType(rawType: String, rawSubType: String, rawParameters: List[(String, String)], raw: String) {
  lazy val `type`: String = rawType.toLowerCase
  lazy val subtype: String = rawSubType.toLowerCase
  lazy val parameters: List[(String, String)] = rawParameters.map(t => (t._1.toLowerCase(), t._2.toLowerCase()))

  /** A string represents media type which is normalized according to RFC7231 */
  // 标准化参数信息：k1/v1;k2/v2
  lazy val normalize: String = {
    val s = s"${`type`}/$subtype"
    if (parameters.nonEmpty) {
      val p: List[String] = parameters.map(t => s"${t._1}=${t._2}")
      s"$s;${p.mkString(";")}"
    }
    else {
      s
    }
  }
}

object MediaType {
  /**
   * Parse a string representing the media type.
   *
   * Grammar used are listed below:
   *
   * {{{
   * media-type    = type "/" subtype *( OWS ";" OWS parameter )
   * type          = token
   * subtype       = token
   * parameter     = token "=" ( token / quoted-string )
   * OWS           = *( SP / HTAB )
   * toke          = 1*tchar
   * quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
   * tchar         = "!" / "#" / "$" / "%" / "&" / "'" / "*"
   *               / "+" / "-" / "." / "^" / "_" / "" / "|" / "~"
   *               / DIGIT / ALPHA
   * }}}
   *
   * @param input the input string
   * @return an optional MediaType
   */
  def parse(input: String): Option[MediaType] = {
    val splits = input.split("/", 2)
    if (splits.length < 2) {
      None
    }
    else {
      val mainType = splits(0).trim
      val splits1 = splits(1).split(";")
      val subtype = splits1(0).trim
      for (parameters <- parseParameters(splits1.toList.tail)) yield {
        MediaType(
          rawType = mainType,
          rawSubType = subtype,
          rawParameters = parameters,
          raw = input.trim
        )
      }
    }
  }

  /**
   * 将以=分隔的字符串列表转化为二元组列表
   *
   * @param strs 包含=符号的字符串列表
   * @return 二元组列表
   */
  private def parseParameters(strs: List[String]): Option[List[(String, String)]] = {
    val builder = ListBuffer[(String, String)]()
    var error = false
    for (str <- strs) {
      parseParameter(str) match {
        case None => error = true

        case Some(p) => builder += p
      }
    }

    if (error) None
    else Some(builder.toList)
  }

  /**
   * 转化参数，将字符串按照=符号分隔，然后将前后两个值的小写形式放到二元组返回
   *
   * @param str 待分析字符串
   * @return 按照=分割之后的小写形式的二元组
   */
  private def parseParameter(str: String): Option[(String, String)] = {
    val splits = str.trim.split("=")
    if (splits.length != 2) {
      None
    }
    else {
      Some(splits(0).trim -> splits(1).trim)
    }
  }
}
